package zcache

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"zcache/consistenthash"
)

// 提供可以被其他节点访问的能力 (基于http)
// 分布式缓存需要实现不同节点间的通信


const (
	defaultBasePath = "/_cache/"
	defaultReplicas = 50
)


// 请求以: http://example.com/_cache/ 开头
type HTTPPool struct {
	// 记录自身的地址, 包括主机名IP和端口, 如: http://example.com
	self string
	// 节点间通信地址的前缀, 默认为: /_cache/
	basePath string
	mu sync.Mutex
	// 一致性哈希的映射, 通过key获取对应的节点
	peers *consistenthash.Map
	// 节点对应的httpGetter
	httpGetters map[string]*httpGetter
}


// 实现PeerGetter接口
type httpGetter struct {
	// 要访问的远程节点地址
	// http://example.com/_cache/
	baseURL string
}


func NewHTTPPool(self string) *HTTPPool {
	return &HTTPPool{
		self: self,
		basePath: defaultBasePath,
	}
}


// 记录日志
func (p *HTTPPool) Log(format string, v ...interface{}) {
	log.Printf("[ + Server %s ] %s", p.self, fmt.Sprintf(format, v...))
}


// HTTP Handler
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 通过URL前缀判断请求是否合法
	if !strings.HasPrefix(r.URL.Path, p.basePath) {
		panic("HTTPPool serving unexpected path: " + r.URL.Path)
	}

	p.Log("%s %s", r.Method, r.URL.Path)

	// 节点之间的访问格式: /<basepath>/<groupname>/<key>
	parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
	if len(parts) != 2 {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	groupName := parts[0]
	key := parts[1]

	// 获取指定的缓存组
	group := GetGroup(groupName)
	if group == nil {
		http.Error(w, "no such group: " + groupName, http.StatusNotFound)
		return
	}

	// 获取指定key的缓存值
	view, err := group.Get(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/octet-stream")
	w.Write(view.ByteSlice())
}


// 通过http与其他节点通信获取缓存数据
func (h *httpGetter) Get(group string, key string) ([]byte, error) {
	// URL
	u := fmt.Sprintf(
		"%v%v/%v",
		h.baseURL,
		url.QueryEscape(group),
		url.QueryEscape(key),
		)

	res, err := http.Get(u)
	if err != nil {
		return nil, err
	}

	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("server returned: %v", res.StatusCode)
	}

	bytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("reading response body: %v", err)
	}

	return bytes, nil
}


// 实例化一致性哈希, 并添加传入的节点
func (p *HTTPPool) Set(peers ...string) {
	p.mu.Lock()
	defer p.mu.Unlock()

	p.peers = consistenthash.New(defaultReplicas, nil)
	p.peers.Add(peers...)

	p.httpGetters = make(map[string]*httpGetter, len(peers))
	for _, peer := range peers {
		// 每个节点都创建一个httpGetter
		p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}
	}
}


// 根据传入的key, 获取对应的节点, 并返回该节点对应的httpGetter
func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
	p.mu.Lock()
	defer p.mu.Unlock()

	if peer := p.peers.Get(key); peer != "" && peer != p.self {
		p.Log("Pick peer %s", peer)
		return p.httpGetters[peer], true
	}

	return nil, false
}
